/*
Kiwi Editor is a framework for building WYSIWYG web editors.
Copyright (C) 2011  Roosevelt de Lima Sardinha

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

Contact the author at roosevelt1@gmail.com (Roosevelt) 
*/

/**
 * Responsible by the management of the editor objects in an edition area.
 * @author Roosevelt de Lima Sardinha
 * @param {Object} editionArea The edition area object that this EOM manages the EOs.
 * @constructor
 */
function editorObjectManager(editionArea){
    var thisObject = this;
    var objectIdArray = new Array;
    var objectArray = new Array;

	/**
	 * The edition area object that this EOM manages the EOs
	 * @type {Object}
	 */
	this.editionArea = editionArea;
    
    // Counters
    var counter = 0;
    var memo = new Array;
    
	var myKeyboard = this.editionArea.workspace.keyboard;
	 
	/**
	 * Enables the use of the keyboard in the edition area.
	 */ 
    this.enableKeyboard = function(){
        myKeyboard.enabled = true;
    }
    
	/**
	 * Disables the use of the keyboard in the edition area.
	 */
    this.disableKeyboard = function(){
        myKeyboard.enabled = false;
    }
    
	/**
	 * Returns the counter. This counter is used to attrib to an EO its id. Preventing th ids from being equal in any case.
	 * @return The actual value of the counter.
	 * @type {Int}
	 */
    this.getCounter = function(){
        counter++;
        return counter;
    }
    
	/**
	 * Sets a value to the counter This counter is used to attrib to an EO its id. Preventing the ids from being equal in any case.
	 * @param {Int} value The value to set to the counter
	 */
	this.setCounter = function(value){
		counter = value;
	}
	
	/**
	 * Creates an EO given the type of EO to be created. After you can append it to the edition area using another method.
	 * @param {String} type The type of EO to be created. The same name of the folder of the tool.
	 * @return The EO created
	 * @type {Object}
	 */
    this.createEO = function(type){
        // Creates an Editor Object of any type
        if (type == null) {
            type = 'Error: EOM -> CreateEO : type cannot be null.';
        }
        var id = this.editionArea.id + '-' + type + '_' + thisObject.getCounter();

		var inheritEO = new EO(id,{
			EOM: this
		});
		
		// Set default propertyBox 
		if (editionArea.workspace.getPropertyBoxByIndex(0)){
			inheritEO.setPropertyBox(editionArea.workspace.getPropertyBoxByIndex(0).id);
		}

		// Set default instructionBox
		if (editionArea.workspace.getInstructionBoxByIndex(0)){
			inheritEO.setInstructionBox(editionArea.workspace.getInstructionBoxByIndex(0).id);
		}		

		var myEO = new window[type]();
		for (var key in inheritEO){
			if (myEO.hasOwnProperty(key) == false) {
				myEO[key] = inheritEO[key];
			}else {
				myEO['user_' + key] = myEO[key];
				myEO[key] = inheritEO[key];
			}
		}
		myEO.type = type;
		myEO.init();

        return myEO;
    }
    
	/**
	 * Returns an EO given its id.
	 * @param {String} id The unique id of the EO.
	 * @return The EO that has the id. Or -1 if it wasn't found. 
	 * @type {Object}
	 */
    this.getEOById = function(id){
        // Returns an editor object that has the Id
        var index = -1;
        for (var i = 0; i < objectIdArray.length; i++) {
            if (objectIdArray[i] == id) {
                index = i;
                break;
            }
        }        
        if (index != -1) {
            return objectArray[index];
        }
        else {
            return index;
        }
    }
    
	/**
	 * The EO that has this HTML Object in its objectList.
	 * @param {String} HTMLObjectId The id of the DOM node we are searching for.
	 * @return The EO that has the HTML Object. Or -1 if it wasn't found.
	 * @type {Object}
	 */
    this.getEOByHTMLObjectId = function(HTMLObjectId){
        // Returns an Editor Object that has the HTML Object with the Id
        var index = -1;
        var EO = null;
        var myId;
        for (var i = 0; i < objectArray.length; i++) {
            EO = objectArray[i];
            for (var j = 0; j < EO.objectList.length; j++) {
                myId = EO.getHTMLObject(EO.objectList[j]).id;
                if (myId == HTMLObjectId) {
                    index = i;
                    break;
                }
            }
        }
        
        if (index != -1) {
            return objectArray[index];
        }
        else {
            return index;
        }
    }
    
	/**
	 * Unselects all EOs.
	 */
    this.unselectAll = function(){
        for (var i = 0; i < objectArray.length; i++) {
            this.unselectEO(objectArray[i].id);
        }
    }
    
	/**
	 * Returns the right coordinate of the object that has the maximum right coordinate.
	 * @return The right coordinate.
	 * @type {Int}
	 */
    this.getMaxRight = function(){
        if (objectArray.length == 0) {
            return 0;
        }
        var divEO = document.getElementById(objectArray[0].id);
        var maxRight = divEO.offsetLeft + divEO.offsetWidth;
        var right = 0;
        for (var i = 1; i < objectArray.length; i++) {
            divEO = document.getElementById(objectArray[i].id);
            right = divEO.offsetLeft + divEO.offsetWidth;
            if (right > maxRight) {
                maxRight = right;
            }
        }
        return maxRight;
    }
    
	/**
	 * Returns the bottom coordinate of the object that has the maximum bottom coordinate.
	 * @return The bottom coordinate.
	 * @type {Int}
	 */
    this.getMaxBottom = function(){
        if (objectArray.length == 0) {
            return 0;
        }
        
        var divEO = document.getElementById(objectArray[0].id);
        var maxBottom = divEO.offsetTop + divEO.offsetHeight;
        var bottom = 0;
        for (var i = 1; i < objectArray.length; i++) {
            var divEO = document.getElementById(objectArray[i].id);
            bottom = divEO.offsetTop + divEO.offsetHeight;
            if (bottom > maxBottom) {
                maxBottom = bottom;
            }
        }
        return maxBottom;
    }
	
    /**
     * Removes an EO.
     * @param {String} id The id of the EO to be removed.
     */
    this.removeEO = function(id){
        // Removes an EO
        var index = -1;
        for (var i = 0; i < objectIdArray.length; i++) {
            if (objectIdArray[i] == id) {
                index = i;
                break;
            }
        }
        
        if (index != -1) {
            var EO = objectArray[index];
			if (EO.propertyBox) {
				EO.propertyBox.removeAllTabs();
			}
            //Removes the object from the document.
            for (var j = 0; j < EO.objectList.length; j++) {
                var HTMLObject = EO.getHTMLObject(objectArray[index].objectList[j]);
                HTMLObject.parentNode.removeChild(HTMLObject);
                delete HTMLObject;
            }
            
            // Remove the div that contains the EO
            document.getElementById(EO.id).parentNode.removeChild(document.getElementById(EO.id));
            
            // Remove the object from the control arrays
            objectArray.splice(index, 1);
            objectIdArray.splice(index, 1);

			if (EO.onRemove) {
				EO.onRemove();
			}
			
            // Remove the EO
            delete EO;
        }
    }
    
	/**
	 * Removes only the selected EOs.
	 * @return The number of EOs removed.
	 * @type {Int}
	 */
    this.removeSelectedEOs = function(){
        var counter = 0;
        // Remove the selected EOs in the edition area.
        for (var i = 0; i < objectArray.length; i++) {
            if (objectArray[i].selected) {
                thisObject.removeEO(objectIdArray[i]);
                i--;
                counter++;
            }
        }
        
        return counter;
    }
	
	/**
	 * Removes all EOs.
	 */
	this.removeAllEOs = function(){
		// Remove every EO in the edition area.
		for(var i=0; i<objectArray.length; i++){
			thisObject.removeEO(objectIdArray[i]);
			i--;	
		}
	}	

	Array.prototype.find = function(content){
		var index = -1; 
		for(var i = 0; i < this.length; i++){
			if (this[i] == content){
				index = i;
			}
		}
		return index;
	}
	
	Array.prototype.apply = function(array){
		for(var i = 0; i < array.length; i++){
			if (this.find(array[i]) == -1){
				this.push(array[i]);
			}
		}
		return this;
	}
	
	function cloneNode(node){
	    var clonedNode = node.nodeType == 3 ? document.createTextNode(node.nodeValue) : node.cloneNode(false);
 	   	// Recurse
    	var child = node.firstChild;
    	while(child) {
        	clonedNode.appendChild(cloneNode(child));
        	child = child.nextSibling;
		}
		return clonedNode;
	}
	
	/**
	 * Copies an EO. It still doesn't function well in Internet Explorer.
	 * @param {String} id The id of the EO to be copied.
	 * @return A copy of the EO.
	 * @type {Object}
	 */
    this.copyEO = function(id){
        // Copies an editor object to the "memory" of the application, for being pasted in another moment.
        var objCopy;
        var obj = thisObject.getEOById(id);
        
        // Copies the object.
        objCopy = thisObject.createEO(obj.type);

		// Copy Most Properties
		for (var key in obj){
			if (obj.objectList.find(key) == -1) {
				objCopy[key] = obj[key];
			}
		}
		var counter = this.getCounter();
		objCopy.id = this.editionArea.id + "-" + obj.type + "_" + counter;
		objCopy.selected = false;
		
		// Copy DOM nodes.
		for(var k=0; k < obj.objectList.length; k++){
			var key = obj.objectList[k] ;
			var HTMLObject = obj[key];
			var HTMLCopyObject = cloneNode(HTMLObject);
			
			objCopy[key] = HTMLCopyObject;	
		}

		var list = this.attribIds(objCopy);
		var idsList = new Array();	
		var key = "";
		for (var i = 0; i < list.length; i++){
			key = list[i][0];	
			objCopy[key] = null;
			objCopy[key] = list[i][2];
			idsList.push = list[i][1];
		}
		
		objCopy.selectorIds = idsList;
				
        memo.push(objCopy);
		
		return objCopy;
    }
    
	/**
	 * Copies all selected editor objects to the "memory" of the application, for being pasted in an another moment.
	 */
    this.copySelectedEOs = function(){
        for (var i = 0; i < objectArray.length; i++) {
            if (objectArray[i].selected == true) {
                thisObject.copyEO(objectIdArray[i]);
            }
        }
    }
	
    /**
     * Cleans the memo array.
     */
    this.cleanMemo = function(){
        
        for (var i = 0; i < memo.length; i++) {
            memo.pop();
            i--;
        }
    }
    
    var memoCopy = new Array;
	
	/**
	 * Returns a copy of the memo array.
	 * @return A copy of the memo array. It has all the EOs in the memo.
	 * @type {Array}
	 */
    this.retrieveMemo = function(){
        memoCopy = memo.slice();
        return memoCopy;
    }
    
	/**
	 * Unregisters an EO from this EOM.The EO won't be managed from this EOM anymore.
	 * @param {Object} EO The EO to be unregistered.  
	 */
	this.unregisterEO = function(EO){
		var index = objectIdArray.find(EO.id);
		objectIdArray.splice(index,1);
		objectArray.splice(index,1);
	}
	
	/**
	 * Registers an EO to be managed by the EOM.
	 * @param {Object} EO The EO to be registered.
	 */
    this.registerEO = function(EO){
        objectIdArray.push(EO.id);
        objectArray.push(EO);
    }
    
	/**
	 * Disables dragging an EO.
	 * @param {String} id The id of the EO that dragging will be disabled.  
	 */
    this.stopDraggingEO = function(id){
        $('#' + id).draggable('disable');
    }
    
	/**
	 * Enables dragging an EO.
	 * @param {String} id The id of the EO that dragging will be enabled.  
	 */	
    this.startDraggingEO = function(id){
        $('#' + id).draggable('enable');
    }
    
	/**
	 * Enables EO resize of one previously disabled.
	 * @param {String} id The id of the EO to be resized.
	 * @param {Int} width The minimum width of the resizement.
	 * @param {Int} height The minimum height of the resizement.
	 */
    this.enableEOResize = function(id, width, height){
        $('#' + id).resizable('enable');
        $('#' + id).resizable('option', 'minWidth', width);
        $('#' + id).resizable('option', 'minHeight', height);
    }
	
	/**
	 * Corrects the position of an object in the edition Area, so it's not going to get outside.
	 * @param {String} id The id of the EO to correct the position
	 */
    this.correctPosition = function(id){
        		
        var divEO = document.getElementById(id);
        
        // If its larger than the editionArea, resizes the editionArea.
        var divEOWidth = divEO.offsetWidth;
        if (editionArea.width < divEOWidth) {
            editionArea.setWidth(divEOWidth);
        }
        
        var divEOHeight = divEO.offsetHeight;
        if (editionArea.height < divEOHeight) {
            editionArea.setHeight(divEOHeight);
        }
        
        //Calculates the difference in the X axis for correction. How much is it outside the editionArea?
        var editionAreaWidth = editionArea.width;
        var divEORight = divEO.offsetLeft + divEO.offsetWidth;
        var diffX = editionAreaWidth - divEORight;
        
        //Calculates the difference in the Y axis for correction. How much is it outside the editionArea?
        var editionAreaHeight = editionArea.height;
        var divEOBottom = divEO.offsetTop + divEO.offsetHeight;
        var diffY = editionAreaHeight - divEOBottom;
        
        // If an object is outside the area, corrects its position.
        if (diffX < 0) {
            divEO.style.left = divEO.offsetLeft + diffX + 'px';
        }
        
        if (diffY < 0) {
            divEO.style.top = divEO.offsetTop + diffY + 'px';
        }
    }
    
	/**
	 * Selects an EO.
	 * @param {String} id The unique id of the EO to be selected.
	 */
    this.selectEO = function(id){
               
		myKeyboard.EOM = this;
		
        var EO = this.getEOById(id);
        
		if (EO.propertyBox) {
			EO.showProperties();
			EO.onShowProperties(EO);
		}
		
		if (EO.instructionBox) {
			EO.instructionBox.setInstruction(EO.help, false);
			EO.onShowInstructions(EO);
		}
		
		if (EO.onSelect) {
			EO.onSelect(EO);
		}
		
        EO.selected = true;
        if (EO.propertyBox) {
            EO.propertyBox.hasFocus = false;
            this.enableKeyboard();
        }
        for (var i = 0; i < EO.objectList.length; i++) {
            $('#' + EO.getHTMLObject(EO.objectList[i]).id).addClass('ui-selected');
        }
        
        var divEO = document.getElementById(id);
        $('#' + divEO.id).addClass('edition-area-eo-selected');
    }
    
	/**
	 * Simulates a click in an EO.
	 * @param {String} id The id of the EO to simulate the click.
	 * @param {Object} event The mouse event of the click, if needed.  
	 */
    this.clickEO = function(id, event){
        // Defines what happens when someone clicks an EO. Default behaviour.
        var ev = event || window.event;
        var EO = this.getEOById(id);

        if (ev != null) {
            if (ev.ctrlKey) {
                if (EO.selected == false) {
                    this.selectEO(EO.id);
                }
            }
            else {
				
				var countSel = 0;
				for (var i = 0; i<objectArray.length; i++){
					if(objectArray[i].selected == true){
						countSel += 1;
					}	
					if (countSel == 2){
						break;
					}
				}
				
				//Checks if more than one object is selected
				if (countSel<2){
					if (EO.propertyBox) {
						EO.propertyBox.removeAllTabs();
					}
	                this.unselectAll();
					
	                if (EO.selected == false) {
	                    this.selectEO(EO.id);
	                }
				}
            }
        }
    }
    
	/**
	 * Unselects an EO.
	 * @param {String} id The id of the EO to be unselected.
	 */
    this.unselectEO = function(id){
        // Defines what to do when an object is unselected.	
		myKeyboard.EOM = this;	
		
        var EO = this.getEOById(id);
		if (EO.instructionBox) {
			EO.instructionBox.clearInstruction();
		}
		if(EO.propertyBox){
			EO.propertyBox.removeAllTabs();
		}

		if (EO.onUnselect) {
			EO.onUnselect(EO);
		}
        EO.selected = false;
        for (var i = 0; i < EO.objectList.length; i++) {
            $('#' + EO.getHTMLObject(EO.objectList[i]).id).removeClass('ui-selected');
        }
        
        var divEO = document.getElementById(id);
		if (divEO) {
			$('#' + divEO.id).removeClass('edition-area-eo-selected');
		}
    }
    
	/**
	 * Sets a property to all selected EOs of a defined type.
	 * @param {String} property The property to be changed
	 * @param {Any} value The value to be set.
	 * @param {String} type The types of objects to be affected.
	 */
    this.setPropertyOfSelected = function(property, value, type){
        for (var i = 0; i < objectArray.length; i++) {
            if (objectArray[i].selected == true) {
                if (objectArray[i].type == type) {
                    objectArray[i].setProperty(property, value);
                }
            }
        }
    }
    
	/**
	 * Adds some value to editionArea jQuery selectable cancel property. Look for jQuery UI Selectable if you need help.
	 * @param {String} value The value to be added to the cancel property.
	 */
    this.addToEditionAreaCancel = function(value){
        var cancel = $('#editionArea').selectable('option', 'cancel');
        if (cancel == '') {
            $('#editionArea').selectable('option', 'cancel', value);
        }
        else {
            $('#editionArea').selectable('option', 'cancel', cancel + ', ' + value);
        }
    };
    
	/**
	 * Gets a JSON with all EOs, its types and properties.
	 * @return A JSON describing all EOs
	 * @type {Object}
	 */
    this.EO_JSON = function(){
        var JSONObject = [];
        for (var i = 0; i < objectArray.length; i++) {
            var type = objectArray[i].type;
            var EOJSON = objectArray[i].JSON();
			JSONObject.push({
				type:type,
				properties:EOJSON
			});
        }
        return JSONObject;
    }
	
    /**
     * Gets all the HTML of the EOs managed by this EOM.
     * @return An HTML containing all DOM nodes and its properties returned by all EOs managed by this EOM.
     * @type {String} 
     */
	this.getEOsHTML = function(){
		var html = "";
        for (var i = 0; i < objectArray.length; i++) {
            html += objectArray[i].HTML();
        }
		var ind = new indenter();
		var indented_html = ind.indentHTML(html);
        return indented_html;
	}
	
	/**
	 * Creates a set of EOs based on a given JSON object
	 * @param {Object} json The JSON used to create the EOs
	 * @return The list of EOs created.
	 * @type {Array}
	 */
    this.createEOsByJSON = function(json){
        var EOList = [];
        var EO = null;
		var EOJSON = null;
        for (var i=0; i < json['eos'].length; i++) {
			EOJSON = json['eos'][i];
			if (window[EOJSON['type-name']] != null){
	            EO = this.createEO(EOJSON['type-name']);
	            editionArea.appendObject(EO);
	            for (var property in EOJSON.properties){
	                EO.setProperty(property, unescape(EOJSON.properties[property]));
	            }
	            EOList.push(EO);
			}
        }
        return EOList;
    }
	
	/**
	 * Sets the correct IDs for the DOM nodes in the idsList
	 * @param {DOM} DOMNode The DOM node to correct the ids.
	 * @param {Array} idsList The list of ids to be changed 
	 */
	this.setDOMIds = function(DOMNode, idsList){
		var children = DOMNode.childNodes;
		var child = null;
		var oldId = "";
		var newId = "";
		
		if (idsList == null){
			idsList = [];
		}

		var newIdsList = new Array();
		var list = new Array();
		var getId = false;
		for (var i = 0; i < children.length; i++) {
			child = children[i];
			
			if (child.id != "") {
				for (var k = 0; k < idsList.length; k++) {
					if (child.id == idsList[k]) {
						oldId = idsList[k];
						getId = true;
					}
				}
			}
						 
			
			if (child.tagName) {
				child.id = child.parentNode.id + "_" + i;
				if (getId) {
					newId = child.id;
					newIdsList.push([oldId, newId, child]);
				}
				list = this.setDOMIds(child, idsList);
				for (var k=0; k<list.length; k++){
					newIdsList.push(list[k]);	
				}
			}
		}
		
		return newIdsList;
	}
	
	/**
	 * Attribe correct ids to the DOM nodes of an EO.
	 * @param {Object} EO The EO to attrib the ids.
	 */
	this.attribIds = function(EO){
		var idEO = EO.id;
		var HTMLObject = null;
		var indexEO = parseInt(idEO.substr(idEO.indexOf("_") + 1));
		var idsList = new Array();
		var newIdsList = new Array();
		var ret = new Array();
		
		
		for (var i = 0 ;  i< EO.selectorList.length; i++){			
			idsList.push(EO[EO.selectorList[i]].id);
		}
		
		for (var i=0; i < EO.objectList.length; i++){
			HTMLObject = EO.getHTMLObject(EO.objectList[i]);
			HTMLObject.id = EO.objectList[i] + "_" + indexEO;
			
			var list = this.setDOMIds(HTMLObject, idsList);
			for (var k=0; k< list.length; k++){
				newIdsList.push(list[k]);
			}
		}
		
		for (var i=0; i < idsList.length; i++){
			for (var k = 0; k < newIdsList.length; k ++){
				if (newIdsList[k][0] == EO[EO.selectorList[i]].id){
					ret.push([EO.selectorList[i], newIdsList[k][1], newIdsList[k][2]]);
				}
			}
		}
		
		return ret;
	}

	/**
	 * Sets the events  of onMouseDown to the DOM nodes of an EO, for enabling selection when you click.
	 * @param {Object} EO The EO that has the DOM nodes to change the event.
	 * @param {Array} idsList Ids of nodes that could be clicked and are not in the selectorList of the EO.
	 */
	this.setSelectors = function(EO, idsList){
		var len = EO.selectorList.length;
		for (var i = 0; i < len; i++) {
			EO[EO.selectorList[i]].onmousedown = function(event){
				EO.EOM.clickEO(EO.id, event);
			};
		}
		
		if (idsList!= null){
			for (var i = 0; i < idsList.length; i++) {
				document.getElementById(idsList[i]).onmousedown = function(event){
					EO.EOM.clickEO(EO.id, event);
				};
			}	
		}
	}

	/**
	 * Changes the snapTo property of all EOs
	 * @param {Int} x The x coordinate of the snapTo.
	 * @param {Int} y The y coordinate of the snapTo.
	 */
	this.setAllEOSnapTo = function(x,y){
		for (var i = 0 ; i < objectArray.length; i++){
			objectArray[i].setSnapTo(x, y);
		} 
	}
	
	/**
	 * Moves all selected objects to the left.
	 * @param {Int} diff The number of pixels to move it to the left. If negative, moves to the right.
	 */
	this.moveSelectedLeft = function(diff){
        for (var i = 0; i < objectArray.length; i++) {
            if (objectArray[i].selected == true) {
                objectArray[i].setLeft(objectArray[i].getLeft() + diff);
            }
        }
	}	

	/**
	 * Moves all selected objects to the top.
	 * @param {Int} diff The number of pixels to move it to the top. If negative, moves to the bottom.
	 */	
	this.moveSelectedTop = function(diff){
        for (var i = 0; i < objectArray.length; i++) {
            if (objectArray[i].selected == true) {
                objectArray[i].setTop(objectArray[i].getTop() + diff);
            }
        }		
	}
	
	/**
	 * Gets a list of all EOs ids
	 * @return A list of ids
	 * @type {Array}
	 */
	this.getEOs = function(){
		// Returns all EOs ids.
		return objectIdArray;
	}
	
	/**
	 * Gets a list of the selected EOs ids
	 * @return A list of ids
	 * @type {Array}
	 */
	this.getSelectedEOs = function(){
		var selectedEOsIds = [];
		for (var i = 0; i < objectArray.length; i++) {
            if (objectArray[i].selected == true) {
                selectedEOsIds.push(objectArray[i].id);
            }
        }
		return  selectedEOsIds;
	}
}
